package com.xy.net;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.internal.$Gson$Types;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.ConnectionPool;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Created by John on 2016/10/31.
 */

public class OkHttpUtils {
    private Handler mHandler;
    private Gson gson;
    private static OkHttpUtils instance;
    private OkHttpClient okHttpClient;
    private Cache cache;
    private Context context;

    private OkHttpUtils(Context context) {
        this.context = context;
        okHttpClient = null;
        mHandler = new Handler(Looper.getMainLooper());
        gson = new Gson();

    }

    public OkHttpClient initNet(boolean isCache) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        cache = isCache(isCache);
        if (cache != null) {
            builder.cache(cache);
        }
        OkHttpClient client = builder.connectTimeout(10, TimeUnit.SECONDS) //连接超时
                .readTimeout(10, TimeUnit.SECONDS)  //读取超时
                .addNetworkInterceptor(new CacheInterceptor())  //缓存插值器
                .connectionPool(new ConnectionPool(8, 10, TimeUnit.SECONDS))  // 这里你可以根据自己的机型设置同时连接的个数和时间，我这里8个，和每个保持时间为10s
                .build();
        return client;
    }

    public Cache isCache(boolean isCache) {
        if (isCache) {
            Cache cache1 = new Cache(context.getCacheDir(), 10240 * 1024);
            return cache1;
        }
        return null;
    }

    //当服务器没有指定缓存头部时，在response中加入cache-control头部  使其支持缓存
    public class CacheInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            return response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    //cache for 2 days
                    .header("Cache-Control", "max-age=" + 60 * 60 * 2 * 24)
                    .build();

        }
    }

    public static OkHttpUtils getInstance(Context context) {
        if (instance == null) {
            synchronized (OkHttpUtils.class) {
                if (instance == null) {
                    instance = new OkHttpUtils(context);
                }
            }
        }
        return instance;
    }

    //同步Get和post
    //***************************************************************************************************************//
    public Response synGet(String url, boolean isCache) throws IOException {
        okHttpClient = initNet(isCache);
        Request request = new Request.Builder().get().url(url).build();
        Call call = okHttpClient.newCall(request);
        Response response = call.execute();
        return response;
    }

    public String synGetString(String url, boolean isCache) throws IOException {
        Response response = synGet(url, isCache);
        return response.body().string();
    }

    public Response synPost(String url, HashMap<String, String> formMap, boolean isCache) throws IOException {
        return synPost(url, formMap, false, isCache);
    }

    public Response synPost(String url, HashMap<String, String> formMap, Boolean isEncode, boolean isCache) throws IOException {
        okHttpClient = initNet(isCache);
        RequestBody requestBody = Map2FormBody(formMap, isEncode);
        Request request = new Request.Builder().post(requestBody).url(url).build();
        Call call = okHttpClient.newCall(request);
        return call.execute();
    }

    public String synPostString(String url, HashMap<String, String> formMap, Boolean isEncode, boolean isCache) throws IOException {
        okHttpClient = initNet(isCache);
        Response response = synPost(url, formMap, isEncode);
        return response.body().string();
    }

    public Response synPostFile(String url, File[] files, String[] fileKeys, Map<String, String> params, boolean isCache) throws IOException {
        okHttpClient = initNet(isCache);
        Request request = buildMultipartFormRequest(url, files, fileKeys, params);
        return okHttpClient.newCall(request).execute();
    }

    //异步Get和post
    //***************************************************************************************************************//
    public void asynGet(String url, boolean isCache, ResultCallBack callBack) {
        okHttpClient = initNet(isCache);
        Log.i("url", url);
        Request request = new Request.Builder().get().url(url).build();
        deliverResult(callBack, request);
    }

    public void asynPost(String url, boolean isCache, Map<String, String> reqeustParams, ResultCallBack callBack) {
        okHttpClient = initNet(isCache);
        RequestBody requestBody = Map2FormBody(reqeustParams, false);
        Request request = new Request.Builder().post(requestBody).url(url).build();
        deliverResult(callBack, request);
    }

    public void asynPost(String url, boolean isCache, Map<String, String> reqeustParams, boolean isEncoding, ResultCallBack callBack) {
        okHttpClient = initNet(isCache);
        RequestBody requestBody = Map2FormBody(reqeustParams, isEncoding);
        Request request = new Request.Builder().post(requestBody).url(url).build();
        deliverResult(callBack, request);
    }

    /**
     * 异步基于post的文件上传，单文件不带参数上传
     *
     * @param url
     * @param callback
     * @param file
     * @param fileKey
     * @throws IOException
     */
    private void _postAsyn(String url, ResultCallBack callback, File file, String fileKey) throws IOException {
        Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null);
        deliverResult(callback, request);
    }

    /**
     * 异步基于post的文件上传，单文件且携带其他form参数上传
     *
     * @param url
     * @param callback
     * @param file
     * @param fileKey
     * @param params
     * @throws IOException
     */
    private void _postAsyn(String url, ResultCallBack callback, File file, String fileKey, Map<String, String> params) throws IOException {
        Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params);
        deliverResult(callback, request);
    }

    public void asynPostFile(String url, File[] files, String[] fileKeys, Map<String, String> params, ResultCallBack callBack) {
        Request request = buildMultipartFormRequest(url, files, fileKeys, params);
        deliverResult(callBack, request);
    }

    public void asynDownload(String url, final String destFileDir, final ResultCallBack callBack) {
        final Request request = new Request.Builder().url(url).get().build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                dealFailue(request, e, callBack);
            }

            @Override
            public void onResponse(Call call, Response response) {
                InputStream in = null;
                byte[] buff = new byte[2048];
                FileOutputStream out = null;
                int len = 0;
                try {
                    in = response.body().byteStream();
                    File file = new File(destFileDir, getFileName(destFileDir));//文件路径，文件名称
                    out = new FileOutputStream(file);
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff, 0, buff.length);
                    }
                    out.flush();
                    //如果下载文件成功，第一个参数为文件的绝对路径
                    sendSuccessResultCallback(file.getAbsolutePath(), callBack);
                } catch (IOException e) {
                    dealFailue(request, e, callBack);
                } finally {
                    try {
                        if (in != null) {
                            in.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    try {
                        if (out != null) {
                            out.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        });
    }

    private String getFileName(String destFileDir) {
        int index = destFileDir.lastIndexOf("/");
        return index > 0 ? destFileDir.substring(index + 1, destFileDir.length()) : destFileDir;
    }


    //多参数请求处理
    private Request buildMultipartFormRequest(String url, File[] files, String[] fileKeys, Map<String, String> params) {
        params = (params == null ? new HashMap<String, String>() : params);
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> next = iterator.next();
            builder.addFormDataPart(next.getKey(), next.getValue());
            builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + next.getKey() + "\""),
                    RequestBody.create(null, next.getValue()));
        }
        if (files != null) {
            RequestBody fileBody = null;
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                String fileName = file.getName();
                fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
                // 根据文件名设置contentType
                builder.addPart(Headers.of("Content-Disposition",
                        "form-data; name=\"" + fileKeys[i] + "\"; filename=\"" + fileName + "\""),
                        fileBody);
            }
        }
        MultipartBody multipartBody = builder.build();
        return new Request.Builder().url(url).post(multipartBody).build();
    }

    //获取MIME类型
    private String guessMimeType(String path) {
        FileNameMap nameMap = URLConnection.getFileNameMap();  //Java获取文件类型Mime Type的各种方法
        String contentTypeFor = nameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }

    //异步请求结果处理
    private void deliverResult(final ResultCallBack callBack, final Request request) {
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                dealFailue(request, e, callBack);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    String data = response.body().string();
                    if (callBack.type == String.class) {
                        sendSuccessResultCallback(data, callBack);
                    } else {
                        Object o = gson.fromJson(data, callBack.type);
                        sendSuccessResultCallback(o, callBack);
                    }
                } catch (IOException e) {
                    dealFailue(response.request(), e, callBack);
                } catch (JsonParseException e) {
                    dealFailue(response.request(), e, callBack);
                }
            }
        });
    }

    /**
     * 处理网络请求错误的情况
     *
     * @param request  请求
     * @param e        异常
     * @param callBack 错误回调
     */
    private void dealFailue(final Request request, final Exception e, final ResultCallBack callBack) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (callBack != null)
                    callBack.onError(request, e);
            }
        });
    }

    /**
     * 处理网络请求成功的返回结果
     *
     * @param data     请求
     * @param callBack 处理结果的回调
     */
    private void sendSuccessResultCallback(final Object data, final ResultCallBack callBack) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (callBack != null)
                    callBack.onResult(data);
            }
        });
    }

    public static abstract class ResultCallBack<T> {
        Type type;

        public ResultCallBack() {
            type = getSuperclassTypeParameter(getClass());
        }

        Type getSuperclassTypeParameter(Class<?> aClass) {
            Type superclass = aClass.getGenericSuperclass(); //返回带有泛型数据的直接父类对象
            if (superclass instanceof Class) {
                throw new RuntimeException("Missing type parameter.");
            }
            ParameterizedType parameterizedType = (ParameterizedType) superclass;  //参数化类型
            /**
             * 由于外部不得不采用匿名类的方式来生成TypeToken<?>,因此,需要获得的泛型类型包含在superclass中。
             * 假如你的匿名类不包含任何的泛式类型,就会抛出一个"Miss type paramter"异常。
             * 后，执行getGenericSuperclass()操作,得到的是一个ParameterizedType 类型的Type。
             * 而这个Type包含几乎关心的所有类型元数据，包括它的拥有者,类名和泛型参数等。
             * 最后，根据静态的canonicalize 方法，规范在泛型内的参数,并且转化成Gson自己给定的数据结构，返回获取到的Type信息。
             */
            return $Gson$Types.canonicalize(parameterizedType.getActualTypeArguments()[0]);    //getActualTypeArguments获取参数化类型的数组，泛型可能有多个
        }

        public abstract void onError(Request request, Exception e);

        public abstract void onResult(T result);
    }

    /**
     * 将map转化成okhttp post 的参数请求体对象
     *
     * @param formMap
     * @param isEncode 是否编码
     * @return
     */
    private RequestBody Map2FormBody(Map<String, String> formMap, Boolean isEncode) {
        FormBody.Builder builder = new FormBody.Builder();
        if (formMap != null) {
            Iterator<Map.Entry<String, String>> iterator = formMap.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, String> next = iterator.next();
                String paramsKey = next.getKey();
                String paramsValue = next.getValue();
                builder = isEncode ? builder.addEncoded(paramsKey, paramsValue) : builder.add(paramsKey, paramsValue);
            }
        }
        return builder.build();
    }
}
